from flask import Flask, request, jsonify, render_template, send_from_directory, send_file
from threading import Thread
from werkzeug.utils import secure_filename
import atexit
from hp_fixar_v01 import config, utils
import json 
import logging
import os
import shutil
import uuid

template_dir = config.template_dir
static_dir = config.static_dir
app = Flask(__name__, template_folder=template_dir, static_folder=static_dir)

# 载入配置参数
app.config['UPLOAD_FOLDER'] = config.UPLOAD_FOLDER
app.config['TEMP_RESULT_FOLDER'] = config.TEMP_RESULT_FOLDER
app.config['ARCHIVE_FOLDER'] = config.ARCHIVE_FOLDER
app.config['REPLACE_RADIUS'] = config.REPLACE_RADIUS
app.config['ALLOWED_EXTENSIONS'] = config.ALLOWED_EXTENSIONS


# 创建必要的目录
for folder in [app.config['UPLOAD_FOLDER'], app.config['TEMP_RESULT_FOLDER'], app.config['ARCHIVE_FOLDER']]:
    os.makedirs(folder, exist_ok=True)

ALLOWED_EXTENSIONS = {'fits', 'fit', 'fz'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

# 日志配置
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
progress_dict = {}  # 存放所有任务进度
file_store_dict = {}  # 临时保存每个任务的文件路径与form表单


@app.after_request
def add_response_headers(response):
    if response.content_type == 'application/json':
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
    return response

@app.route('/')
def home_redirect():
    return render_template('index.html')

@app.route('/index/')
def index():
    return render_template('index.html')

@app.route('/results/')
#@app.route('/results')
def show_results():
    """显示处理结果页面路由
    返回: 
        - 渲染results.html模板
        - 包含三类文件列表: hotpixels, corrected和txtfiles
        - 包含完整的metadata数据
    """
    """
    显示结果页面。

    Args:
        无

    Returns:
        HTML页面：渲染的'results.html'页面，包含hotpixels, corrected, txtfiles, metadata等变量。

    Raises:
        无

    """
    try:
        metadata_path = os.path.join(app.config['TEMP_RESULT_FOLDER'], 'metadata.json')
        metadata = utils.load_metadata(metadata_path)

        hotpixels, corrected, txtfiles = [], [], []

        for m in metadata:
            filetype = m.get("type")
            filename = m.get("filename")
            if not filename or not filetype:
                continue
            file_path = os.path.join(app.config['TEMP_RESULT_FOLDER'], filename)
            if not os.path.exists(file_path):
                logger.warning(f"文件不存在：{filename}")
                continue

            if filetype == 'hotpixels':
                hotpixels.append(filename)
            elif filetype == 'corrected':
                corrected.append(filename)
            elif filetype == 'params':
                txtfiles.append(filename)

        return render_template('results.html',
                               hotpixels=hotpixels,
                               corrected=corrected,
                               txtfiles=txtfiles,
                               metadata=metadata)

    except Exception as e:
        logger.error(f"读取临时结果目录失败: {e}")
        return render_template('results.html', hotpixels=[], corrected=[], txtfiles=[], metadata=[])

@app.route('/history/')
#@app.route('/history')
def show_history():
	"""显示历史记录页面路由
	返回:
		- 渲染history.html模板
		- 包含三类文件列表: hotpixels, corrected和txtfiles
		- 包含完整的metadata数据
	"""
	try:
		metadata_path = os.path.join(app.config['ARCHIVE_FOLDER'], 'metadata.json')
		metadata = utils.load_metadata(metadata_path)

		# 确保 metadata 是列表
		if not isinstance(metadata, list):
			logger.warning(f"历史 metadata 格式异常，非列表，路径: {metadata_path}")
			metadata = []

		hotpixels, corrected, txtfiles = [], [], []

		for m in metadata:
			filetype = m.get("type")
			filename = m.get("filename")
			if not filename or not filetype:
				continue

			file_path = os.path.join(app.config['ARCHIVE_FOLDER'], filename)
			if not os.path.exists(file_path):
				logger.warning(f"历史文件不存在：{filename}")
				continue

			if filetype == 'hotpixels':
				hotpixels.append(filename)
			elif filetype == 'corrected':
				corrected.append(filename)
			elif filetype == 'params':
				txtfiles.append(filename)

		return render_template(
			'history.html',
			hotpixels=hotpixels,
			corrected=corrected,
			txtfiles=txtfiles,
			metadata=metadata
		)

	except Exception as e:
		logger.error(f"读取历史目录失败: {e}")
		return render_template('history.html', hotpixels=[], corrected=[], txtfiles=[], metadata=[])

@app.route('/download/<filename>')
def download_file(filename):
    """文件下载路由
    参数:
        - filename: 要下载的文件名
    返回:
        - 文件下载流
        - 404如果文件不存在
        - 500如果服务器错误
    """
    try:
        referrer = request.referrer or ""
        folder = app.config['ARCHIVE_FOLDER'] if 'history' in referrer else app.config['TEMP_RESULT_FOLDER']
        file_path = os.path.join(folder, filename)
        # logger.info(f"请求下载文件: {filename}，目录: {folder}")
        if os.path.exists(file_path):
            return send_from_directory(folder, filename, as_attachment=True, mimetype='application/octet-stream')
        
        logger.error(f"文件未找到: {file_path}")
        return "文件未找到", 404
    except Exception as e:
        logger.error(f"下载文件出错: {filename}, 错误: {str(e)}")
        return "服务器错误", 500

@app.route('/api/batch-download', methods=['POST'])
def batch_download():
    """批量下载文件路由，并打包所选文件与其metadata.json
    方法: POST
    参数(JSON):
        - filenames: 要下载的文件名列表
    返回:
        - ZIP文件流
    """
    data = request.get_json()

    if not data:
        return jsonify({'success': False, 'error': '请求必须是JSON格式'}), 400
    
    files = data.get('filenames')
    if not files or not isinstance(files, list):
        return jsonify({'success': False, 'error': '未指定要下载的文件'}), 400

    try:
        referrer = request.referrer or ""
        folder = app.config['ARCHIVE_FOLDER'] if 'history' in referrer else app.config['TEMP_RESULT_FOLDER']
        metadata_path = os.path.join(folder, 'metadata.json')

        # 加载原始metadata.json
        all_metadata = utils.load_metadata(metadata_path)

        # 仅保留被选中文件的metadata条目
        selected_metadata = [entry for entry in all_metadata if entry.get("filename") in files]

        # 创建内存中的zip文件
        import io, zipfile, json
        zip_buffer = io.BytesIO()
        with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
            # 添加所有用户选择的文件
            for fname in files:
                file_path = os.path.join(folder, fname)
                if os.path.exists(file_path):
                    zip_file.write(file_path, arcname=fname)
            
            # 添加对应的metadata.json（只包含所选文件）
            zip_file.writestr('metadata.json', json.dumps(selected_metadata, indent=2, ensure_ascii=False))

        zip_buffer.seek(0)
        return send_file(zip_buffer, mimetype='application/zip', as_attachment=True, download_name='selected_files_with_metadata.zip')

    except Exception as e:
        logger.error(f"批量下载出错: {str(e)}")
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/process', methods=['POST', 'OPTIONS'])
def process_image():
    if request.method == 'OPTIONS':
        return jsonify({'success': True}), 200

    files = request.files.getlist('files') if 'files' in request.files else [request.files['file']]
    if not files or all(file.filename == '' for file in files):
        return jsonify({'success': False, 'error': '未提供有效的FITS文件'}), 400

    task_id = str(uuid.uuid4())
    progress_dict[task_id] = {
        'processed': 0,
        'total': len(files),
        'current_file': '',
        'status': 'pending',  # 还未开始
        'results': [],
        'metadata_list': []
    }

    utils.clear_temp_results(app.config['TEMP_RESULT_FOLDER'])

    saved_filepaths = []
    for file in files:
        filename = secure_filename(file.filename)
        if not utils.allowed_file(filename):
            continue
        filepath = os.path.join(app.config['TEMP_RESULT_FOLDER'], filename)
        file.save(filepath)
        saved_filepaths.append((filepath, filename))

    # 保存文件列表和表单数据到字典，等待启动任务时用
    progress_dict[task_id]['saved_filepaths'] = saved_filepaths
    progress_dict[task_id]['form_data'] = request.form.to_dict()

    return jsonify({'success': True, 'task_id': task_id})

def process_batch_task(task_id):
    saved_filepaths = progress_dict[task_id].get('saved_filepaths', [])
    form_data = progress_dict[task_id].get('form_data', {})
    progress_dict[task_id]['status'] = 'processing'

    for filepath, filename in saved_filepaths:
        progress_dict[task_id]['current_file'] = filename
        try:
            result, metadata_entries = utils.process_task(
                filepath=filepath,
                filename=filename,
                method=form_data.get('method'),
                form_data=form_data,
                output_folder=app.config['TEMP_RESULT_FOLDER']
            )
            progress_dict[task_id]['results'].append(result)
            progress_dict[task_id]['metadata_list'].extend(metadata_entries)
        except Exception as e:
            logger.error(f"处理文件 {filename} 失败: {e}")

        progress_dict[task_id]['processed'] += 1

    progress_dict[task_id]['status'] = 'done'

@app.route('/api/process/start/<task_id>', methods=['POST'])
def start_process_task(task_id):
    if task_id not in progress_dict:
        return jsonify({'success': False, 'error': '任务不存在'}), 404
    if progress_dict[task_id]['status'] != 'pending':
        return jsonify({'success': False, 'error': '任务已启动或已完成'}), 400

    thread = Thread(target=process_batch_task, args=(task_id,))
    thread.start()

    return jsonify({'success': True, 'message': '任务已启动'})

@app.route('/api/progress/<task_id>')
def get_progress(task_id):
    """
    查询任务进度
    参数:
        task_id: 前端提交任务后返回的唯一任务ID
    返回:
        JSON: 当前进度百分比、状态等信息
    """
    progress = progress_dict.get(task_id)
    if not progress:
        return jsonify({'success': False, 'error': '任务不存在'}), 404

    percent = int((progress['processed'] / max(progress['total'], 1)) * 100)
    return jsonify({
        'success': True,
        'progress': percent,
        'processed': progress['processed'],
        'total': progress['total'],
        'current_file': progress.get('current_file', ''),
        'status': progress.get('status', 'processing')
    })

@app.route('/api/result/<task_id>')
def get_result(task_id):
    progress = progress_dict.get(task_id)
    if not progress:
        return jsonify({'success': False, 'error': '任务不存在'}), 404
    if progress['status'] != 'done':
        return jsonify({'success': False, 'error': '任务未完成'}), 400

    # 保存metadata文件
    metadata_path = os.path.join(app.config['TEMP_RESULT_FOLDER'], 'metadata.json')
    with open(metadata_path, 'w', encoding='utf-8') as meta_f:
        json.dump(progress['metadata_list'], meta_f, ensure_ascii=False, indent=4)

    return jsonify({'success': True, 'results': progress['results']})

@app.route('/api/get-results-metadata', methods=['GET'])
def get_results_metadata():
    """获取结果元数据路由
    方法: GET
    返回(JSON):
        - 所有结果文件的元数据
        - 按字段、对象、日期和方法分类
        - 文件类型统计
    """
    try:
        results_meta_path = os.path.join(app.config['TEMP_RESULT_FOLDER'], 'metadata.json')
        results_metadata = utils.load_metadata(results_meta_path)

        hotpixels = [f for f in results_metadata if f.get("type") == "hotpixels"]
        corrected = [f for f in results_metadata if f.get("type") == "corrected"]
        txtfiles = [f for f in results_metadata if f.get("type") == "params"]

        fields = sorted({m.get('field_id', '') for m in results_metadata if m.get('field_id')})
        objects = sorted({m.get('object', '') for m in results_metadata if m.get('object')})
        dates = sorted({m.get('date_obs', '') for m in results_metadata if m.get('date_obs')})
        methods = sorted({m.get('method', '') for m in results_metadata if m.get('method')})

        return jsonify({
            'success': True,
            'files': results_metadata,
            'fields': list(fields),
            'objects': list(objects),
            'dates': list(dates),
            'methods': list(methods),
            'summary': {
                'hotpixels': len(hotpixels),
                'corrected': len(corrected),
                'params': len(txtfiles)
            }
        })

    except Exception as e:
        logger.error(f"获取结果元数据失败: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/get-history-metadata', methods=['GET'])
def get_history_metadata():
    """获取历史记录元数据路由
    方法: GET
    返回(JSON):
        - 所有历史文件的元数据
        - 按字段、对象、日期和方法分类
        - 文件类型统计
    """
    try:
        archive_meta_path = os.path.join(app.config['ARCHIVE_FOLDER'], 'metadata.json')
        archive_metadata = utils.load_metadata(archive_meta_path)

        hotpixels = [f for f in archive_metadata if f.get("type") == "hotpixels"]
        corrected = [f for f in archive_metadata if f.get("type") == "corrected"]
        txtfiles = [f for f in archive_metadata if f.get("type") == "params"]

        fields = sorted({m.get('field_id', '') for m in archive_metadata if m.get('field_id')})
        objects = sorted({m.get('object', '') for m in archive_metadata if m.get('object')})
        dates = sorted({m.get('date_obs', '') for m in archive_metadata if m.get('date_obs')})
        methods = sorted({m.get('method', '') for m in archive_metadata if m.get('method')})

        return jsonify({
            'success': True,
            'files': archive_metadata,
            'fields': list(fields),
            'objects': list(objects),
            'dates': list(dates),
            'methods': list(methods),
            'summary': {
                'hotpixels': len(hotpixels),
                'corrected': len(corrected),
                'params': len(txtfiles)
            }
        })

    except Exception as e:
        logger.error(f"获取历史元数据失败: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route("/api/batch-move-to-history", methods=["POST"])
def batch_move_to_history():
    """批量移动文件到历史记录路由
    方法: POST
    参数(JSON):
        - filenames: 要移动的文件名列表
    返回(JSON):
        - 移动的文件数量和列表
        - 400如果请求无效
    """
    data = request.get_json()
    filenames = data.get("filenames", [])

    if not filenames or not isinstance(filenames, list):
        return jsonify({"error": "Missing or invalid filenames"}), 400

    src_folder = app.config['TEMP_RESULT_FOLDER']
    dst_folder = app.config['ARCHIVE_FOLDER']
    src_meta_path = os.path.join(src_folder, "metadata.json")
    dst_meta_path = os.path.join(dst_folder, "metadata.json")

    # 加载 metadata
    src_metadata = utils.load_metadata(src_meta_path)
    if not isinstance(src_metadata, list):
        src_metadata = []

    dst_metadata = utils.load_metadata(dst_meta_path)
    if not isinstance(dst_metadata, list):
        dst_metadata = []

    moved_entries = []
    remaining_src_metadata = []

    for entry in src_metadata:
        filename = entry.get("filename")
        if filename in filenames:
            # 重命名避免冲突
            new_filename = utils.force_numbered_name(filename, dst_folder)
            src_path = os.path.join(src_folder, filename)
            dst_path = os.path.join(dst_folder, new_filename)

            # 文件存在则移动
            if os.path.exists(src_path):
                shutil.move(src_path, dst_path)

            # 更新元数据字段
            entry["filename"] = new_filename
            entry["base_name"] = os.path.splitext(new_filename)[0]

            if "file_types" in entry:
                if isinstance(entry["file_types"], dict):
                    entry["file_types"]["name"] = new_filename
                elif isinstance(entry["file_types"], list):
                    for ft in entry["file_types"]:
                        if ft.get("name") == filename:
                            ft["name"] = new_filename

            moved_entries.append(entry)
        else:
            remaining_src_metadata.append(entry)

    # 写入 metadata
    utils.write_metadata(dst_meta_path, dst_metadata + moved_entries)
    utils.write_metadata(src_meta_path, remaining_src_metadata)


    return jsonify({
        "success": True,
        "moved_count": len(moved_entries),
        "moved_files": [entry["filename"] for entry in moved_entries]
    })

@app.route('/api/delete-file', methods=['POST'])
def delete_file():
	try:
		data = request.get_json()
		filename = data.get('filename')
		scope = data.get('scope')

		if not filename or not scope:
			return jsonify({'success': False, 'error': '缺少 filename 或 scope'}), 400

		folder = app.config['TEMP_RESULT_FOLDER'] if scope == 'results' else \
				 app.config['ARCHIVE_FOLDER'] if scope == 'history' else None
		if not folder:
			return jsonify({'success': False, 'error': '无效的 scope'}), 400

		filepath = os.path.join(folder, filename)
		deleted = False
		if os.path.exists(filepath):
			os.remove(filepath)
			deleted = True

		metadata_path = os.path.join(folder, 'metadata.json')
		if os.path.exists(metadata_path):
			with open(metadata_path, 'r', encoding='utf-8') as f:
				metadata = json.load(f)
			# metadata 是纯列表，删除对应条目
			metadata = [entry for entry in metadata if entry.get('filename') != filename]
			with open(metadata_path, 'w', encoding='utf-8') as f:
				json.dump(metadata, f, ensure_ascii=False, indent=2)

		if deleted:
			return jsonify({'success': True, 'deleted': filename})
		else:
			return jsonify({'success': False, 'error': '文件未找到'}), 404

	except Exception as e:
		logger.error(f"[{scope}] 删除文件失败: {e}")
		return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/batch-delete', methods=['POST'])
def batch_delete():
	try:
		data = request.get_json()
		filenames = data.get('filenames')
		scope = data.get('scope')

		if not filenames or not isinstance(filenames, list) or not scope:
			return jsonify({'success': False, 'error': '缺少 filenames 或 scope'}), 400

		folder = app.config['TEMP_RESULT_FOLDER'] if scope == 'results' else \
				 app.config['ARCHIVE_FOLDER'] if scope == 'history' else None
		if not folder:
			return jsonify({'success': False, 'error': '无效的 scope'}), 400

		deleted_files = []
		for filename in filenames:
			filepath = os.path.join(folder, filename)
			if os.path.exists(filepath):
				os.remove(filepath)
				deleted_files.append(filename)

		metadata_path = os.path.join(folder, 'metadata.json')
		if os.path.exists(metadata_path):
			with open(metadata_path, 'r', encoding='utf-8') as f:
				metadata = json.load(f)
			# 纯列表格式，过滤删除的文件
			metadata = [entry for entry in metadata if entry.get('filename') not in filenames]
			with open(metadata_path, 'w', encoding='utf-8') as f:
				json.dump(metadata, f, ensure_ascii=False, indent=2)

		if deleted_files:
			return jsonify({'success': True, 'deleted': deleted_files})
		else:
			return jsonify({'success': False, 'error': '未找到任何文件'}), 404

	except Exception as e:
		logger.error(f"[{scope}] 批量删除失败: {e}")
		return jsonify({'success': False, 'error': str(e)}), 500


@atexit.register
def on_exit_cleanup():
    utils.clear_temp_results(app.config['TEMP_RESULT_FOLDER'])

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=6667)
